/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// view.c -- player eye positioning

#include "quakedef.h"


/*

The view is allowed to move slightly from it's TRUE position for bobbing,
but if it exceeds 8 pixels linear distance (spherical, not box), the list of
entities sent from the server may not include everything in the pvs, especially
when crossing a water boudnary.

*/

cvar_t *scr_ofsx;
cvar_t *scr_ofsy;
cvar_t *scr_ofsz;

cvar_t *cl_rollspeed;
cvar_t *cl_rollangle;

cvar_t *cl_bob;
cvar_t *cl_bobcycle;
cvar_t *cl_bobup;

cvar_t *v_kicktime;
cvar_t *v_kickroll;
cvar_t *v_kickpitch;

cvar_t *v_iyaw_cycle;
cvar_t *v_iroll_cycle;
cvar_t *v_ipitch_cycle;

cvar_t *v_iyaw_level;
cvar_t *v_iroll_level;
cvar_t *v_ipitch_level;

cvar_t *v_idlescale;

cvar_t *crosshair;
cvar_t *crosshaircolor;

cvar_t *cl_crossx;
cvar_t *cl_crossy;

cvar_t *gl_cshiftpercent;

cvar_t *v_centermove;
cvar_t *v_centerspeed;

cvar_t *v_gamma;

float	v_dmg_time, v_dmg_roll, v_dmg_pitch;
float	v_oldstepz, v_newstepz;
float	v_stepchangetime;
float	v_punchangles[2][3];

extern	int			in_forward, in_forward2, in_back;


/*
===============
V_CalcRoll

Used by view and sv_user
===============
*/
float V_CalcRoll (vec3_t angles, vec3_t velocity)
{
	float	sign;
	float	side;
	float	value;
	vec3_t	forward, right, up;

	AngleVectors (angles, forward, right, up);
	side = Vector3Dot (velocity, right);
	sign = side < 0 ? -1 : 1;
	side = fabs (side);

	value = cl_rollangle->value;

	if (side < cl_rollspeed->value)
	{
		if (cl_rollspeed->value)
			side = side * value / cl_rollspeed->value;
		else return 0;
	}
	else side = value;

	return side * sign;
}


/*
===============
V_CalcBob

===============
*/
float V_CalcBob (void)
{
	float	bob;
	float	cycle;

	if (!cl_bobcycle->value) return 0;
	if (!cl_bobup->value) return 0;
	if (!(1.0 - cl_bobup->value)) return 0;

	cycle = cl.time - (int) (cl.time / cl_bobcycle->value) * cl_bobcycle->value;
	cycle /= cl_bobcycle->value;

	if (cycle < cl_bobup->value)
		cycle = M_PI * cycle / cl_bobup->value;
	else cycle = M_PI + M_PI * (cycle - cl_bobup->value) / (1.0 - cl_bobup->value);

	// bob is proportional to velocity in the xy plane
	// (don't count Z, or jumping messes it up)
	bob = sqrt (cl.velocity[0] * cl.velocity[0] + cl.velocity[1] * cl.velocity[1]) * cl_bob->value;
	bob = bob * 0.3 + bob * 0.7 * sin (cycle);

	if (bob > 4)
		bob = 4;
	else if (bob < -7)
		bob = -7;

	return bob;

}


/*
==============================================================================

						PALETTE FLASHES

==============================================================================
*/


void V_SetCshift (cshift_t *cs, int r, int g, int b, int pct)
{
	cs->destcolor[0] = r;
	cs->destcolor[1] = g;
	cs->destcolor[2] = b;
	cs->percent = pct;
	cs->initialpercent = pct;
	cs->inittime = cl.time;
}


byte v_blend[4];		// rgba 0.0 - 1.0


/*
=================
V_CheckGamma
=================
*/
BOOL V_CheckGamma (void)
{
	return FALSE;
}



/*
===============
V_ParseDamage
===============
*/
void V_ParseDamage (void)
{
	int		armor, blood;
	vec3_t	from;
	int		i;
	vec3_t	forward, right, up;
	entity_t	*ent;
	float	side;
	float	count;

	armor = MSG_ReadByte ();
	blood = MSG_ReadByte ();

	for (i = 0; i < 3; i++)
		from[i] = MSG_ReadCoord ();

	// if no damage was taken then don't add a shift
	if (!armor && !blood) return;

	if ((count = blood * 0.5 + armor * 0.5) < 10)
		count = 10;

	cl.faceanimtime = cl.time + 0.2;		// but sbar face into pain frame

	cl.cshifts[CSHIFT_DAMAGE].percent += 5 * count;

	if (cl.cshifts[CSHIFT_DAMAGE].percent < 0) cl.cshifts[CSHIFT_DAMAGE].percent = 0;
	if (cl.cshifts[CSHIFT_DAMAGE].percent > 150) cl.cshifts[CSHIFT_DAMAGE].percent = 150;

	cl.cshifts[CSHIFT_DAMAGE].initialpercent = cl.cshifts[CSHIFT_DAMAGE].percent;
	cl.cshifts[CSHIFT_DAMAGE].inittime = cl.time;

	cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = ((200 * armor) + (255 * blood)) / (armor + blood);
	cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = (100 * armor) / (armor + blood);
	cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = (100 * armor) / (armor + blood);

	// calculate view angle kicks
	ent = cl.entities[cl.viewentity];

	VectorSubtract (from, ent->origin, from);
	VectorNormalize (from);

	AngleVectors (ent->angles, forward, right, up);

	side = Vector3Dot (from, right);
	v_dmg_roll = count * side * v_kickroll->value;

	side = Vector3Dot (from, forward);
	v_dmg_pitch = count * side * v_kickpitch->value;

	v_dmg_time = cl.time + v_kicktime->value;
}


/*
==================
V_cshift_f
==================
*/
void V_cshift_f (void)
{
	V_SetCshift (&cl.cshifts[CSHIFT_VCSHIFT], atoi (Cmd_Argv (1)), atoi (Cmd_Argv (2)), atoi (Cmd_Argv (3)), atoi (Cmd_Argv (4)));
}


/*
==================
V_BonusFlash_f

When you run over an item, the server sends this command
==================
*/
void V_BonusFlash_f (void)
{
	V_SetCshift (&cl.cshifts[CSHIFT_BONUS], 215, 186, 69, 50);
}


/*
=============
V_CalcBlend
=============
*/
void V_CalcBlend (void)
{
	float r = 0;
	float g = 0;
	float b = 0;
	float a = 0;
	float a2;
	int j;

	for (j = 0; j < NUM_CSHIFTS; j++)
	{
		if (!gl_cshiftpercent->value) continue;
		if (!(a2 = ((cl.cshifts[j].percent * gl_cshiftpercent->value) / 100.0) / 255.0)) continue;

		a = a + a2 * (1 - a);
		a2 = a2 / a;

		r = r * (1 - a2) + cl.cshifts[j].destcolor[0] * a2;
		g = g * (1 - a2) + cl.cshifts[j].destcolor[1] * a2;
		b = b * (1 - a2) + cl.cshifts[j].destcolor[2] * a2;
	}

	// allow them to scale the blend
	a *= (255.0f * gl_polyblend->value);

	// RGBA ordering for D3D10
	if (r > 255) v_blend[0] = r; else if (r < 0) v_blend[0] = 0; else v_blend[0] = r;
	if (g > 255) v_blend[1] = g; else if (g < 0) v_blend[1] = 0; else v_blend[1] = g;
	if (b > 255) v_blend[2] = b; else if (b < 0) v_blend[2] = 0; else v_blend[2] = b;
	if (a > 255) v_blend[3] = a; else if (a < 0) v_blend[3] = 0; else v_blend[3] = a;
}


/*
=============
V_UpdatePalette
=============
*/
void V_UpdatePalette (void)
{
	// add the powerup cshifts
	V_SetCshift (&cl.cshifts[CSHIFT_QUAD], 0, 0, 255, (cl.items & IT_QUAD) ? 30 : 0);
	V_SetCshift (&cl.cshifts[CSHIFT_SUIT], 0, 255, 0, (cl.items & IT_SUIT) ? 20 : 0);
	V_SetCshift (&cl.cshifts[CSHIFT_RING], 100, 100, 100, (cl.items & IT_RING) ? 100 : 0);
	V_SetCshift (&cl.cshifts[CSHIFT_PENT], 255, 255, 0, (cl.items & IT_PENT) ? 30 : 0);

	// this should never happen
	if (cl.cshifts[CSHIFT_DAMAGE].inittime > cl.time) cl.cshifts[CSHIFT_DAMAGE].inittime = cl.time;
	if (cl.cshifts[CSHIFT_BONUS].inittime > cl.time) cl.cshifts[CSHIFT_BONUS].inittime = cl.time;

	// cshift drops are based on absolute time since the shift was initiated; this is to allow SCR_UpdateScreen to be called multiple times between cl.time changes
	cl.cshifts[CSHIFT_DAMAGE].percent = cl.cshifts[CSHIFT_DAMAGE].initialpercent - (cl.time - cl.cshifts[CSHIFT_DAMAGE].inittime) * 150;
	cl.cshifts[CSHIFT_BONUS].percent = cl.cshifts[CSHIFT_BONUS].initialpercent - (cl.time - cl.cshifts[CSHIFT_BONUS].inittime) * 100;

	// and lower-bound it so that the blend calc doesn't mess up
	if (cl.cshifts[CSHIFT_DAMAGE].percent < 0) cl.cshifts[CSHIFT_DAMAGE].percent = 0;
	if (cl.cshifts[CSHIFT_BONUS].percent < 0) cl.cshifts[CSHIFT_BONUS].percent = 0;
}


/*
==============================================================================

						VIEW RENDERING

==============================================================================
*/

float angledelta (float a)
{
	a = anglemod (a);

	if (a > 180)
		a -= 360;

	return a;
}

/*
==================
CalcGunAngle
==================
*/
void CalcGunAngle (void)
{
	// what an odd piece of code this was.
	// yaw = ref.ViewAngles[YAW];
	// yaw = angledelta(yaw - ref.ViewAngles[YAW]) * 0.4;
	// in other words - "yaw = 0;" - and likewise for pitch.

	cl.viewent.angles[YAW] = ref.ViewAngles[YAW];
	cl.viewent.angles[PITCH] = -ref.ViewAngles[PITCH];

	cl.viewent.angles[ROLL] -= v_idlescale->value * sin (cl.time * v_iroll_cycle->value) * v_iroll_level->value;
	cl.viewent.angles[PITCH] -= v_idlescale->value * sin (cl.time * v_ipitch_cycle->value) * v_ipitch_level->value;
	cl.viewent.angles[YAW] -= v_idlescale->value * sin (cl.time * v_iyaw_cycle->value) * v_iyaw_level->value;
}

/*
==============
V_BoundOffsets
==============
*/
void V_BoundOffsets (void)
{
	entity_t	*ent;

	ent = cl.entities[cl.viewentity];

	// absolutely bound refresh reletive to entity clipping hull
	// so the view can never be inside a solid wall
	if (ref.ViewOrigin[0] < ent->origin[0] - 14)
		ref.ViewOrigin[0] = ent->origin[0] - 14;
	else if (ref.ViewOrigin[0] > ent->origin[0] + 14)
		ref.ViewOrigin[0] = ent->origin[0] + 14;

	if (ref.ViewOrigin[1] < ent->origin[1] - 14)
		ref.ViewOrigin[1] = ent->origin[1] - 14;
	else if (ref.ViewOrigin[1] > ent->origin[1] + 14)
		ref.ViewOrigin[1] = ent->origin[1] + 14;

	if (ref.ViewOrigin[2] < ent->origin[2] - 22)
		ref.ViewOrigin[2] = ent->origin[2] - 22;
	else if (ref.ViewOrigin[2] > ent->origin[2] + 30)
		ref.ViewOrigin[2] = ent->origin[2] + 30;
}

/*
==============
V_AddIdle

Idle swaying
==============
*/
void V_AddIdle (void)
{
	ref.ViewAngles[ROLL] += v_idlescale->value * sin (cl.time * v_iroll_cycle->value) * v_iroll_level->value;
	ref.ViewAngles[PITCH] += v_idlescale->value * sin (cl.time * v_ipitch_cycle->value) * v_ipitch_level->value;
	ref.ViewAngles[YAW] += v_idlescale->value * sin (cl.time * v_iyaw_cycle->value) * v_iyaw_level->value;
}


/*
==============
V_CalcViewRoll

Roll is induced by movement and damage
==============
*/
void V_CalcViewRoll (void)
{
	float	side;

	if (v_kicktime->value)
	{
		side = V_CalcRoll (cl.entities[cl.viewentity]->angles, cl.velocity);
		ref.ViewAngles[ROLL] += side;

		if (v_dmg_time > cl.time)
		{
			ref.ViewAngles[ROLL] += (v_dmg_time - cl.time) / v_kicktime->value * v_dmg_roll;
			ref.ViewAngles[PITCH] += (v_dmg_time - cl.time) / v_kicktime->value * v_dmg_pitch;
		}
	}

	if (cl.stats[STAT_HEALTH] <= 0)
	{
		ref.ViewAngles[ROLL] = 80;	// dead view angle
		return;
	}
}


/*
==================
V_CalcIntermissionRefdef

==================
*/
void V_CalcIntermissionRefdef (void)
{
	entity_t	*ent, *view;
	float		old;

	// ent is the player model (visible when out of body)
	ent = cl.entities[cl.viewentity];

	// view is the weapon model (only visible from inside body)
	view = &cl.viewent;

	VectorCopy (ent->origin, ref.ViewOrigin);
	VectorCopy (ent->angles, ref.ViewAngles);
	view->model = NULL;

	// allways idle in intermission
	old = v_idlescale->value;
	v_idlescale->value = 1;
	V_AddIdle ();
	v_idlescale->value = old;
}


/*
==================
V_CalcRefdef

==================
*/
void V_CalcRefdef (double frametime)
{
	entity_t	*ent, *view;
	int			i;
	float		forward[3], right[3], up[3];
	float		angles[3];
	float		bob;

	// should this be global and reset when a new map loads?
	static float punch[] = {0, 0, 0};

	// ent is the player model (visible when out of body)
	ent = cl.entities[cl.viewentity];

	// view is the weapon model (only visible from inside body)
	view = &cl.viewent;

	// transform the view offset by the model's matrix to get the offset from
	// model origin for the view
	ent->angles[YAW] = cl.viewangles[YAW];	// the model should face the view dir
	ent->angles[PITCH] = -cl.viewangles[PITCH];	// the model should face the view dir

	bob = V_CalcBob ();

	// refresh position
	VectorCopy (ent->origin, ref.ViewOrigin);
	ref.ViewOrigin[2] += cl.viewheight + bob;

	// never let it sit exactly on a node line, because a water plane can
	// dissapear when viewed with the eye exactly on it.
	// the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis
	ref.ViewOrigin[0] += 1.0 / 32;
	ref.ViewOrigin[1] += 1.0 / 32;
	ref.ViewOrigin[2] += 1.0 / 32;

	VectorCopy (cl.viewangles, ref.ViewAngles);
	V_CalcViewRoll ();
	V_AddIdle ();

	// offsets
	angles[PITCH] = -ent->angles[PITCH];	// because entity pitches are actually backward
	angles[YAW] = ent->angles[YAW];
	angles[ROLL] = ent->angles[ROLL];

	AngleVectors (angles, forward, right, up);

	for (i = 0; i < 3; i++)
		ref.ViewOrigin[i] += scr_ofsx->value * forward[i] + scr_ofsy->value * right[i] + scr_ofsz->value * up[i];

	V_BoundOffsets ();

	// set up gun position
	VectorCopy (cl.viewangles, view->angles);

	CalcGunAngle ();

	VectorCopy (ent->origin, view->origin);
	view->origin[2] += cl.viewheight;

	for (i = 0; i < 3; i++)
		view->origin[i] += forward[i] * bob * 0.4;

	view->origin[2] += bob;
	view->origin[2] += 1.5f;

	view->model = cl.model_precache[cl.stats[STAT_WEAPON]];
	view->frame = cl.stats[STAT_WEAPONFRAME];
	view->colormap = vid.colormap;

	// set up the refresh position
	for (i = 0; i < 3; i++)
	{
		if (punch[i] != v_punchangles[0][i])
		{
			// speed determined by how far we need to lerp in 1/10th of a second
			float delta = (v_punchangles[0][i] - v_punchangles[1][i]) * frametime * 10;

			if (delta > 0)
				punch[i] = min2 (punch[i] + delta, v_punchangles[0][i]);
			else if (delta < 0)
				punch[i] = max2 (punch[i] + delta, v_punchangles[0][i]);
		}
	}

	VectorAdd (ref.ViewAngles, punch, ref.ViewAngles);

	// smooth out stair step ups
	if (cl.onground && ent->origin[2] - v_newstepz > 0)
	{
		v_newstepz = v_oldstepz + (cl.time - v_stepchangetime) * 160;

		if (v_newstepz > ent->origin[2])
		{
			v_stepchangetime = cl.time;
			v_newstepz = v_oldstepz = ent->origin[2];
		}

		if (ent->origin[2] - v_newstepz > 12)
		{
			v_stepchangetime = cl.time;
			v_newstepz = v_oldstepz = ent->origin[2] - 12;
		}

		ref.ViewOrigin[2] += v_newstepz - ent->origin[2];
		view->origin[2] += v_newstepz - ent->origin[2];
	}
	else
	{
		v_oldstepz = v_newstepz = ent->origin[2];
		v_stepchangetime = cl.time;
	}
}


/*
==================
V_RenderView

The player's clipping box goes from (-16 -16 -24) to (16 16 32) from
the entity origin, so any view position inside that will be valid
==================
*/

void V_RenderView (double frametime)
{
	// don't allow cheats in multiplayer
	if (cl.maxclients > 1)
	{
		Cvar_SetValue (scr_ofsx, 0);
		Cvar_SetValue (scr_ofsy, 0);
		Cvar_SetValue (scr_ofsz, 0);
	}

	// update entities if connected
	if (cls.state == ca_connected)
	{
		CL_RelinkEntities ();
		CL_UpdateTEnts ();
	}

	if (cl.intermission)
	{
		// intermission / finale rendering
		V_CalcIntermissionRefdef ();
	}
	else
	{
		if (!cl.paused /* && (sv.maxclients > 1 || key_dest == key_game) */)
			V_CalcRefdef (frametime);
	}

	R_RenderView ();
}

//============================================================================

/*
=============
V_Init
=============
*/
void V_Init (void)
{
	Cmd_AddCommand ("v_cshift", V_cshift_f);
	Cmd_AddCommand ("bf", V_BonusFlash_f);

	scr_ofsx = Cvar_Get ("scr_ofsx", "0");
	scr_ofsy = Cvar_Get ("scr_ofsy", "0");
	scr_ofsz = Cvar_Get ("scr_ofsz", "0");

	cl_rollspeed = Cvar_Get ("cl_rollspeed", "200");
	cl_rollangle = Cvar_Get ("cl_rollangle", "2");

	cl_bob = Cvar_Get ("cl_bob", "0.02");
	cl_bobcycle = Cvar_Get ("cl_bobcycle", "0.6");
	cl_bobup = Cvar_Get ("cl_bobup", "0.5");

	v_kicktime = Cvar_Get ("v_kicktime", "0.5");
	v_kickroll = Cvar_Get ("v_kickroll", "0.6");
	v_kickpitch = Cvar_Get ("v_kickpitch", "0.6");

	v_iyaw_cycle = Cvar_Get ("v_iyaw_cycle", "2");
	v_iroll_cycle = Cvar_Get ("v_iroll_cycle", "0.5");
	v_ipitch_cycle = Cvar_Get ("v_ipitch_cycle", "1");

	v_iyaw_level = Cvar_Get ("v_iyaw_level", "0.3");
	v_iroll_level = Cvar_Get ("v_iroll_level", "0.1");
	v_ipitch_level = Cvar_Get ("v_ipitch_level", "0.3");

	v_idlescale = Cvar_Get ("v_idlescale", "0");

	crosshair = Cvar_Get ("crosshair", "1", CVAR_ARCHIVE);
	crosshaircolor = Cvar_Get ("crosshaircolor", "95", CVAR_ARCHIVE);

	cl_crossx = Cvar_Get ("cl_crossx", "0");
	cl_crossy = Cvar_Get ("cl_crossy", "0");

	gl_cshiftpercent = Cvar_Get ("gl_cshiftpercent", "100");

	v_centermove = Cvar_Get ("v_centermove", "0.15");
	v_centerspeed = Cvar_Get ("v_centerspeed", "500");

	v_gamma = Cvar_Get ("gamma", "1", CVAR_ARCHIVE);
}


void V_NewMap (void)
{
	int i;

	Vector3Set (v_punchangles[0], 0, 0, 0);
	Vector3Set (v_punchangles[1], 0, 0, 0);

	for (i = 0; i < NUM_CSHIFTS; i++)
	{
		cl.cshifts[i].initialpercent = 0;
		cl.cshifts[i].inittime = 0;
		cl.cshifts[i].percent = 0;
	}

	v_dmg_time = 0;
	v_oldstepz = v_newstepz = 0;
	v_stepchangetime = 0;
}

